
#pragma once

#include <sstream>
#include <iomanip>

static inline std::string to_hex(uint8_t* data, size_t len)
{
    std::ostringstream oss;
    oss << std::hex << std::uppercase << std::setfill('0');
    for (size_t i = 0; i < len; i++)
    {
        oss << std::setw(2) << (unsigned int)data[i];
    }
    return oss.str();
}

static inline std::vector<uint8_t> to_bytes(const std::string& in)
{
    std::vector<uint8_t> bytes{};
    size_t               length = in.length();
    std::stringstream    hexStringStream;

    hexStringStream >> std::hex;
    for (size_t strIndex = 0; strIndex < length;)
    {
        const char tmpStr[3] = {in[strIndex++], in[strIndex++], 0};

        hexStringStream.clear();
        hexStringStream.str(tmpStr);

        int tmpValue = 0;
        hexStringStream >> tmpValue;
        bytes.push_back(static_cast<uint8_t>(tmpValue));
    }
    return bytes;
}

inline bool is_space(char c)
{
    return c == ' ' || c == '\t';
}
static inline char to_lower(char c)
{
    return 'A' <= c && c <= 'Z' ? c + 'a' - 'A' : c;
}
static inline char to_upper(char c)
{
    return 'a' <= c && c <= 'z' ? c - 'a' - 'A' : c;
}

static inline std::vector<std::string> split(const std::string& str,
                                             const std::string& delim,
                                             const bool         trim_empty = false)
{
    size_t                   pos, last_pos = 0, len;
    std::vector<std::string> tokens;

    while (true)
    {
        pos = str.find(delim, last_pos);
        if (pos == std::string::npos)
        {
            pos = str.size();
        }

        len = pos - last_pos;
        if (!trim_empty || len != 0)
        {
            tokens.push_back(str.substr(last_pos, len));
        }

        if (pos == str.size())
        {
            break;
        }
        else
        {
            last_pos = pos + delim.size();
        }
    }

    return tokens;
}

static inline std::vector<std::string> compact(const std::vector<std::string>& tokens)
{
    std::vector<std::string> compacted;

    for (size_t i = 0; i < tokens.size(); ++i)
    {
        if (!tokens[i].empty())
        {
            compacted.push_back(tokens[i]);
        }
    }

    return compacted;
}

static inline std::string join(const std::vector<std::string>& tokens,
                               const std::string&              delim,
                               const bool                      trim_empty = false)
{
    if (trim_empty)
    {
        return join(compact(tokens), delim, false);
    }
    else
    {
        std::stringstream ss;
        for (size_t i = 0; i < tokens.size() - 1; ++i)
        {
            ss << tokens[i] << delim;
        }
        ss << tokens[tokens.size() - 1];

        return ss.str();
    }
}

static inline std::string replace_all(const std::string& source,
                                      const std::string& target,
                                      const std::string& replacement)
{
    return join(split(source, target, false), replacement, false);
}

static inline bool starts_with(const std::string& str, const std::string& test)
{
    size_t pos = str.find_first_of(test);
    return pos == 0;
}

static inline bool ends_with(const std::string& str, const std::string& test)
{
    size_t pos = str.find_last_of(test);
    return pos == str.size() - 1;
}

static inline std::string to_lower(const std::string& s)
{
    auto ret = std::string{};
    for (const auto c : s)
        ret += to_lower(c);
    return ret;
}

static inline std::string to_upper(const std::string& s)
{
    auto ret = std::string{};
    for (const auto c : s)
        ret += to_upper(c);
    return ret;
}

static inline std::string trim_left(const std::string& s)
{
    auto first = s.begin(), last = s.end();
    while (is_space(*first) && first != last)
    {
        ++first;
    }
    return {first, last};
}

static inline std::string trim_right(const std::string& s)
{
    auto first = s.begin(), last = first + s.size() - 1;
    while (is_space(*last) && first != last)
    {
        --last;
    }
    return {first, last + 1};
}

static inline std::string trim(const std::string& s)
{
    return trim_left(trim_right(s));
}

#include <mutex>
#include <condition_variable>

template<typename T>
class SyncChannel
{
public:
    bool recv_notify(T& val)
    {
        //std::this_thread::sleep_for(std::chrono::seconds(5));
        std::unique_lock<std::mutex> locker(mutex_);

        if (ready_)
        {
            val    = std::move(value_);
            ready_ = false;

            cv_.notify_one();

            return true;
        }
        else
        {
            return false;
        }
    }

    std::cv_status send_wait(T& val, long ms)
    {
        std::unique_lock<std::mutex> locker(mutex_);

        value_ = std::move(val);
        ready_ = true;

        auto status = cv_.wait_for(mutex_, std::chrono::milliseconds(ms));
        if (status == std::cv_status::timeout)
        {
            val    = std::move(value_);
            ready_ = false;
        }

        return status;
    }

private:
    T    value_;
    bool ready_{false};

    std::mutex                  mutex_;
    std::condition_variable_any cv_;
};
